package net.towee.server.authentication.impl;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URI;
import java.util.Locale;

import javax.inject.Inject;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import net.towee.model.account.AccountData;
import net.towee.model.account.AccountIdentity;
import net.towee.model.account.impl.AccountDataImpl;
import net.towee.model.account.impl.AccountIdentityImpl;
import net.towee.model.account.impl.LoginzaAccountImpl;
import net.towee.server.CoreSettings;
import net.towee.server.authentication.EmailSender;
import net.towee.server.authentication.LoginzaAuth;
import net.towee.server.authentication.LoginzaResponse;
import net.towee.server.authentication.PasswordDigest;
import net.towee.server.authentication.SessionAuthManager;
import net.towee.server.gxp.LoginzaJSResponse;
import net.towee.server.persistence.AccountStore;
import net.towee.server.util.RandomBase64Generator;
import net.towee.shared.exceptions.PersistenceException;

import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.mail.EmailException;
import org.apache.commons.mail.HtmlEmail;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.bson.types.ObjectId;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.gson.Gson;
import com.google.gxp.base.GxpContext;
import com.google.inject.name.Named;
import com.mycila.event.internal.guava.base.Strings;

/**
 * Implementation of LoginzaAuth interface that authenticate
 * user by Loginza API.
 * @author c58
 */
public class LoginzaAuthServlet extends HttpServlet implements LoginzaAuth {
	private static final long serialVersionUID = -5024820169469055043L;
	private static final Logger LOG = LoggerFactory.getLogger(LoginzaAuthServlet.class);
	
	private final AccountStore accountStore;
	private final SessionAuthManager sessionManager;
	private final EmailSender emailSender;
	
	private final RandomBase64Generator random = new RandomBase64Generator();
		
	@Inject
	public LoginzaAuthServlet(AccountStore accountStore, SessionAuthManager sessionManager,
			EmailSender emailSender){
		this.accountStore = accountStore;
		this.sessionManager = sessionManager;
		this.emailSender = emailSender;
	}
	
	@Override
	public void doPost(HttpServletRequest request, HttpServletResponse response){
		if(request.getParameter("token") != null) {
			LoginzaResponse logResp = getLoginzaUserByToken( request.getParameter("token") );
			
			if(logResp != null)
				try {
					authenticateUser(logResp, request.getSession(), response.getWriter());
					response.sendRedirect("/");
				} catch (IOException e) {
					LOG.error("Filed to write response: ", e);
				}
		}
	}

	@Override
	public boolean authenticateUser(LoginzaResponse acc, HttpSession session, Appendable out ) {
		if( sessionManager.isLoggedIn( session ) ) {
			// Attach account
			accountStore.attachLoginzaAccount(sessionManager.getLoggedInUser(session, null), 
					new LoginzaAccountImpl(acc.getIdentify(), acc.getProvider()));

			// Send response
			sendJSResponse(acc, out);
			LOG.info("Account authenticated and Loginza account attached");
		}
		else {
			AccountData account = null;
			
			// Try to get account with given loginza response data
			try {
				account = accountStore.getAccountByLoginza( new LoginzaAccountImpl( acc.getIdentify(), acc.getProvider() ) );
			} catch (PersistenceException e) {				
				// Create account and send information email
				account = createNewAccount(acc, random.next(8));
			}
			
			// Authenticate session
			sessionManager.setLoggedInUser(session, account.getId());
			LOG.info("Account authenticated");
		}
		
		return true;
	}

	@Override
	public AccountData createNewAccount(LoginzaResponse user, String password) {
		// Create identity
		AccountIdentity id = new AccountIdentityImpl(new ObjectId().toString());
		id.setFirstName( user.getFirstName() );
		id.setLastName( user.getLastName() );
		
		// Create account
		AccountData acc = new AccountDataImpl( id );
		acc.getAttachedAccounts().addAccount(user.getIdentify(), user.getProvider());
		PasswordDigest dgst = new PasswordDigest(password.toCharArray());
		acc.setSaltDigest(dgst.getSalt(), dgst.getDigest());
		
		// If email exists add, check for existing account
		// with tih email in db
		if( !Strings.isNullOrEmpty( user.getEmail() ) ) {
			try {
				acc = accountStore.getAccountByEmail( user.getEmail() );
				accountStore.attachLoginzaAccount(acc.getId(), new LoginzaAccountImpl(user.getIdentify(), user.getProvider()));
			} catch (PersistenceException e) {
				// Send an email message about new account
				if(sendInformationEmail(user, password))
					acc.addEmail( user.getEmail() );
			}
		}
		
		// Create account
		accountStore.createAccount(acc);
		
		return acc;
	}

	@Override
	public boolean sendInformationEmail(LoginzaResponse user, String password) {
		return emailSender.sendRegistrationEmail(user.getFirstName()+" "+user.getLastName(), user.getEmail(), password, null);
	}
	
	@Override
	public void sendJSResponse(LoginzaResponse resp, Appendable out) {
		try {
			GxpContext gc = new GxpContext(Locale.ENGLISH);
			LoginzaJSResponse.write(out, gc, resp.getIdentify(), resp.getProvider(), resp.getFirstName()+" "+resp.getLastName());
		} catch (IOException e) {
			LOG.error("Filed to wtire response loginza JS: ",e);
		}
		
	}

	@Override
	public LoginzaResponse getLoginzaUserByToken(String token) {
		// Get needed options
		String sig = DigestUtils.md5Hex(token + "78e0c8adcf7738c6b754af87ba7e9499");
		String wid = "1411";

		// Send request
		BufferedReader in = null;
		try {
			// Prepare request
			HttpClient client = new DefaultHttpClient();
			HttpGet checkRequest = new HttpGet();
			checkRequest.setURI(new URI("http://loginza.ru/api/authinfo?token="	+ token + "&id=" + wid + "&sig=" + sig));

			// Send request
			HttpResponse checkResponse = client.execute(checkRequest);
			in = new BufferedReader(new InputStreamReader( checkResponse.getEntity().getContent()) );

			// Get user info
			Gson json = new Gson();
			LoginzaResponse resp = json.fromJson(in, LoginzaResponse.class);
			
			if(resp.getErrorType() != null){
				LOG.error("Loginza token authentication error \""+resp.getErrorType()+"\": "+resp.getErrorMessage());
				return null;
			}
			
			return resp;
		} catch (Exception e) {
			LOG.error("Loginza getting response error: ", e);
		} finally {
			if (in != null) {
				try {
					in.close();
				} catch (IOException e) {
					LOG.error("Loginza while closing connection error: ", e);
				}
			}
		}
		
		return null;
	}
	
	@Override
	public LoginzaResponse getLoginzaUserByJSON(String str) {
		Gson json = new Gson();
		return json.fromJson(str, LoginzaResponse.class);
	}
}
